package com.blt.govdata.advice;

import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.plugins.pagination.Pagination;
import com.blt.govdata.core.web.NeedLoginException;
import com.google.common.collect.ImmutableMap;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.validation.ConstraintViolationException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

import static java.util.Objects.isNull;

@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if (isNull(body))
            return null;

        final Page<?> page = (Page<?>) body;
        final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();

        builder.put("previous", page.hasPrevious());
        builder.put("next", page.hasNext());
        builder.put("pages", page.getPages());
        builder.put("total", page.getTotal());
        builder.put("size", page.getRecords().size());
        builder.put("body", page.getRecords());

        if (!page.getClass().equals(Page.class)) {
            for (final Object obj : this.findChildFiled(page.getClass())) {
                final Field f = (Field) obj;
                builder.put(f.getName(), ReflectionUtils.getField(f, page));
            }
        }
        return builder.build();
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {

        return Pagination.class.isAssignableFrom(returnType.getMethod().getReturnType())
                && (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), RestController.class)
                || returnType.hasMethodAnnotation(ResponseBody.class));
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    protected Object exception(ConstraintViolationException e, HandlerMethod handlerMethod) {

        final Optional<String> message = e.getConstraintViolations().stream()
                .map(c -> c.getConstraintDescriptor())
                .map(c -> c.getMessageTemplate())
                .findFirst();

        return ImmutableMap.of("message", message.orElseGet(() -> e.getClass().getName()));
    }

    @ExceptionHandler()
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    protected Object exception(Exception e, HandlerMethod handlerMethod) {

        if (e instanceof NeedLoginException) throw (NeedLoginException) e;

        e.printStackTrace();
        final String message = e.getMessage();
        return ImmutableMap.of("message", Objects.nonNull(message) ? message : e.getClass().getName());
    }

    @ExceptionHandler()
    @ResponseStatus(value = HttpStatus.NETWORK_AUTHENTICATION_REQUIRED)
    protected Object exception(NeedLoginException e, HandlerMethod handlerMethod) {

        final String message = e.getMessage();
        return ImmutableMap.of("message", Objects.nonNull(message) ? message : e.getClass().getName());
    }

    private Object[] findChildFiled(Class<?> clazz) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(f -> !Modifier.isStatic(f.getModifiers()))
                .map(f -> {
                    ReflectionUtils.makeAccessible(f);
                    return f;
                })
                .toArray();
    }
}
